今日關鍵字:combineReducers
預想中這個App會有兩個地方需要讀取語言
由於設定頁面與導覽列或者首頁並非相鄰元件,比起使用props
傳遞,這裡選擇以redux進行操作
現在的store的結構長這樣
{
allAnime: [...]
}
加入語言後應該變成
{
allAnime: [...],
language: 'xx'
}
如果直接更改reducer的內容
會出現一個語意上的問題
我的reducer或action叫做AnimeReducer
,AnimeAction
…等
塞進對language的操作看起來...怪怪的
這裡我選擇新增另一組reducer及action,並使用combineReducers組合
參數為一個物件,寫法是
combineReducers({ reducerA, reducerB })
等價於
combineReducers({ reducerA: reducerA, reducerB: reducerB })
這裡當然可以更換key值
combineReducers({ keyA: reducerA, keyB: reducerB })
這裡我當然選擇更換key值的版本
combineReducers(
{
allAnime: AnimeReducer,
language: languageReducer
}
)
代入AnimeReducer
的狀態後
combineReducers(
{
allAnime: {
allAnime: [...]
},
language: languageReducer
}
)
結果跟預想的不同,多了一層allAnime
也就是說,combineReducers
會使得store的結構多包一層
所以這裡需要把AnimeReducer
的結構從
{
allAnime: [...]
}
去掉一層變成
[...]
同樣的道理,新寫的language部分也不用以物件的方式,而應該是以字串的形式
調整初始值與return的結構
const initState: Array<Anime> = []
const animeReducer = (state = initState, action: animeActions.AnimeAction) => {
const allAnimeCopy = [...state]
const Favorite: Array<Anime> = []
switch (action.type) {
case animeActions.GET_ALL_ANIMATE_SUCCESS:
return action.payload?.allAnime
case animeActions.RENEW_DATA:
for (let i = 0; i < state.length; i += 1) {
if (action.payload!.anime!.id === allAnimeCopy[i].id) {
allAnimeCopy[i] = action.payload!.anime!
}
if (allAnimeCopy[i].isFavorite || allAnimeCopy[i].isReminding) {
Favorite.push(allAnimeCopy[i])
}
}
storeFavorite(Favorite)
return allAnimeCopy
export const getLanguage = async () => {
try {
const lang = await AsyncStorage.getItem('language')
return lang !== null ? JSON.parse(lang) : 'zh-TW' // 預設使用繁體中文
} catch (error) {
return 'zh-TW'
}
}
export const storeLanguage = async (language: string) => {
try {
const jsonLanguage = JSON.stringify(language)
await AsyncStorage.setItem('language', jsonLanguage)
} catch (e) {
// saving error
}
}
import { Action } from 'redux'
import { call, put, takeEvery } from 'redux-saga/effects'
import { getLanguage } from '../../data/local'
export interface LanguageAction extends Action {
type: string
payload?: {
language?: string
}
}
export const RENEW_LANGUAGE = 'RENEW_LANGUAGE'
export const renewLanguage = (language: string) => ({
type: RENEW_LANGUAGE,
payload: {
language
}
})
export const GET_LANGUAGE_BEGIN = 'GET_LANGUAGE_BEGIN'
export const getLanguageBegin = () => ({
type: GET_LANGUAGE_BEGIN
})
export const GET_LANGUAGE_SUCCESS = 'GET_LANGUAGE_SUCCESS'
export const getLanguageSuccess = (language: string) => ({
type: GET_LANGUAGE_SUCCESS,
payload: {
language
}
})
function* getLocalLanguage() {
const language = yield call(() => getLanguage().then((result) => result))
yield put(getLanguageSuccess(language))
}
function* languageSaga() {
yield takeEvery(GET_LANGUAGE_BEGIN, getLocalLanguage)
}
export default languageSaga
languageSaga
import { all } from 'redux-saga/effects'
import animeSaga from '../action/animeAction'
import languageSaga from '../action/languageAction'
function* rootSaga() {
yield all([animeSaga(), languageSaga()])
}
export default rootSaga
import * as LanguageActions from '../action/languageAction'
import { storeLanguage } from '../../data/local'
const initState = 'zh-TW'
const languageReducer = (
state = initState,
action: LanguageActions.LanguageAction
) => {
switch (action.type) {
case LanguageActions.GET_LANGUAGE_SUCCESS:
return action.payload!.language
case LanguageActions.RENEW_LANGUAGE:
storeLanguage(action.payload!.language!)
return action.payload!.language
default:
return state
}
}
export default languageReducer
新增rootReducer
import { combineReducers } from 'redux'
import animeReducer from './animeReducer'
import languageReducer from './languageReducer'
import loadingReducer from './loadingReducer'
import { Anime } from '../../data/content'
export interface RootStateType {
allAnime: Array<Anime>
language: string
isLoading: boolean
}
const rootReducer = combineReducers({
allAnime: animeReducer,
language: languageReducer,
isLoading: loadingReducer
})
export default rootReducer
store中改使用rootReducer
...
// import animeReducer from '../reducer/animeReducer'
import rootReducer from '../reducer/rootReducer'
const store = createStore(
// animeReducer,
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware))
)
...
store的調整到此結束
最後要選擇在哪個元件中進行語言的初次讀取及切換
選擇的條件有
Provider
內(才能讀取store內的狀態)useTranslation
是hook
,需要在Function Component中呼叫...
const Navigation = () => {
const { t, i18n } = useTranslation()
const dispatch = useDispatch()
const language = useSelector((state: RootStateType) => state.language)
useEffect(() => {
dispatch(getLanguageBegin())
dispatch(getAllAnimeBegin())
}, [])
useEffect(() => {
i18n.changeLanguage(language)
}, [language])
由於明天開始要進行設定頁面的改造,對於redux的相關操作就等明天一起處理